package main

import (
	"archive/zip"
	"bufio"
	"bytes"
	"crypto/md5"
	"encoding/hex"
	"fmt"
	"io"
	"log"
	"os"
	"path/filepath"
	"sort"
	"strings"
)

type Manifest map[string]string

type JarInfo struct {
	Path string // jar的绝对路径
	*Manifest
}

const MainFestName = "META-INF/MANIFEST.MF" // jar版本信息文件的位置

var (
	jarsMap = map[string]JarInfo{} // key = jar名字+版本
)

func main() {

	jarPkgs := make([]JarPkg, 0, 10)

	// 程序需要先获取服务类型的路径
	pathMap := make(map[string]string)
	pathMap["jboss"] = "/root/jboss/jboss-as-7.1.1.F"

	// 返回该路径下的所有jar
	jars, err := JarPathsForSvc(pathMap["jboss"])
	if err != nil {
		fmt.Println(err)
	}
	fmt.Println("===========jar path=============")
	fmt.Println(jars)
	fmt.Println("========================")

	for _, jarPath := range jars {
		jarInfo := JarPkg{
			Name:    filepath.Base(jarPath),
			PathAbs: jarPath,
			MD5:     getFileMD5(jarPath),
		}
		manifest, err := GetManifest(jarPath)
		if err != nil {
			fmt.Println(err)
		}
		for k, v := range *manifest {
			if k == "Implementation-Version" {
				jarInfo.Version = v
			}
			if k == "Main-Class" {
				jarInfo.IsExec = true
				jarInfo.SvcType = "应用程序"
			}
		}
		jarPkgs = append(jarPkgs, jarInfo)
	}

	sort.Sort(js(jarPkgs))
	for _, pkg := range jarPkgs {
		fmt.Printf("\n name = %s,  version = %s, path =%s\n", pkg.Name, pkg.Version, pkg.PathAbs)
	}

	//marshal, err := json.Marshal(jarPkgs)
	//if err != nil {
	//	fmt.Println(err)
	//}
	//fmt.Println(string(marshal))
}

// JarPathsForSvc java服务根路径，从这下面去检索出所有jar
func JarPathsForSvc(svcPath string) ([]string, error) {
	jarFiles := make([]string, 0, 10)
	_, err := os.Stat(svcPath)
	if err != nil {
		return nil, err
	}
	err = filepath.Walk(svcPath, func(fileName string, fi os.FileInfo, err error) error {
		if fi.IsDir() {
			return nil
		}
		if strings.HasSuffix(fi.Name(), ".jar") {
			jarFiles = append(jarFiles, fileName)
		}
		return nil
	})
	return jarFiles, err
}

func GetManifest(fileName string) (*Manifest, error) {
	jar, err := readFromFile(fileName)
	if err != nil {
		return nil, err
	}
	return jar.Manifest, nil
}

func readFromFile(fileName string) (*JarInfo, error) {
	var (
		err  error
		file *os.File
		fi   os.FileInfo
		r    *zip.Reader
	)
	if file, err = os.Open(fileName); err != nil {
		return nil, err
	}
	defer file.Close()
	if fi, err = file.Stat(); err != nil {
		return nil, err
	}
	if r, err = zip.NewReader(file, fi.Size()); err != nil {
		return nil, err
	}
	return readFromReader(r)
}

func readFromReader(r *zip.Reader) (*JarInfo, error) {
	var (
		part   []byte
		prefix bool
		lines  []string
	)
	jar := new(JarInfo)
	lineNumber := -1
	for _, f := range r.File {
		// TODO 这个文件下拿不到，可以继续从 pop.properties 下去获取
		if f.Name == MainFestName {
			rc, err := f.Open()
			if err != nil {
				return nil, err
			}
			reader := bufio.NewReader(rc)
			buffer := bytes.NewBuffer(make([]byte, 0))
			for {
				if part, prefix, err = reader.ReadLine(); err != nil {
					break
				}
				if len(part) == 0 {
					continue
				}
				buffer.Write(part)
				if !prefix {
					//lines = append(lines, buffer.String())
					line := buffer.String()
					if line[0] == ' ' {
						lines[lineNumber] = lines[lineNumber] + line
					} else {
						lines = append(lines, line)
						lineNumber = lineNumber + 1
					}
					buffer.Reset()
				}
			}
			if err == io.EOF {
				err = nil
			}
			rc.Close()
		}
	}
	jar.Manifest = makeManifestMap(lines)
	return jar, nil
}

func makeManifestMap(lines []string) *Manifest {
	manifestMap := make(Manifest)
	for _, line := range lines {
		if !strings.Contains(line, "Implementation-Version") {
			continue
		}
		i := strings.Index(line, ":")
		if i == -1 {
			log.Println("Not properties file?? This line missing colon (:):", line)
			continue
		}
		key := strings.TrimSpace(line[:i])
		value := strings.TrimSpace(line[i+1:])
		manifestMap[key] = value
	}
	return &manifestMap
}

// JarPkg 单个jar的详细信息
type JarPkg struct {
	Name    string `json:"name"`
	Type    string `json:"type"`
	IsExec  bool   `json:"is_exec"`
	Version string `json:"version"`
	PathAbs string `json:"path_abs"`
	SvcType string `json:"svc_type"` // 服务类型
	MD5     string `json:"md_5"`
}

type js []JarPkg

func (j js) Len() int {
	return len(j)
}
func (j js) Less(i, n int) bool {
	return j[i].Name < j[n].Name
}
func (j js) Swap(i, n int) {
	j[i], j[n] = j[n], j[i]
}

// 计算文件的MD5值
func getFileMD5(fileName string) string {
	f, err := os.Open(fileName)
	if err != nil {
		log.Println(err)
		return ""
	}
	defer f.Close()
	md := md5.New()
	io.Copy(md, f)
	return hex.EncodeToString(md.Sum(nil))
}
